import { forkHandler, isEqualDeep, nextTick, wait } from '@/utils';
import Axios from 'axios';
import { useDebugValue, useRef, useState } from 'react';
import { useCall, useEffectRef, useSimpleMemo, useWillMountEffect } from '.';

/** 异步请求钩子 */
const useReq = (configOrService, options = null) => {
  const startRef = useRef(false);
  const cancelRef = useRef();
  const argsRef = useRef();
  const configOrServiceRef = useEffectRef(configOrService);
  const optionsRef = useEffectRef(options);
  const promiseRef = useRef(Promise.resolve(null));
  const [status, setStatus] = useState('canceled'); // loading / success / fail / canceled
  const [uploadProgress, upProgress] = useState(0);
  const [downloadProgress, downProgress] = useState(0);
  const [result, setData] = useState(null);
  const [error, setError] = useState(null);

  useDebugValue(result);

  const cancel = useCall((reason) => {
    const cancelFn = cancelRef.current?.cancel;
    setTimeout(() => {
      cancelFn?.(reason);
    });
  });

  useWillMountEffect(() => {
    return cancel;
  });

  const done = useCall(
    forkHandler((res) => {
      console.log('done');
      setData(res);
      setStatus('success');
    }, true),
  );

  const fail = useCall(
    forkHandler((error) => {
      setError(error);
      if (Axios.isCancel(error)) {
        console.log('useReq canceled', error);
      }
      setStatus(Axios.isCancel(error) ? 'canceled' : 'fail');
    }, true),
  );

  const onUploadProgress = useCall(
    forkHandler(({ lengthComputable = false, loaded, total }) => {
      if (!lengthComputable) return upProgress(-1);
      upProgress(Math.round((loaded * 1e4) / total) / 100);
    }, options?.onUploadProgress),
  );

  const onDownloadProgress = useCall(
    forkHandler(({ lengthComputable = false, loaded, total }) => {
      if (!lengthComputable) return downProgress(-1);
      downProgress(Math.round((loaded * 1e4) / total) / 100);
    }, options?.onDownloadProgress),
  );

  const start = useCall(
    async (service, opts, ...args) => {
      // 如果下一个请求参数与当前请求参数一样，不会触发请求，返回上一个请求的promise
      if (isEqualDeep(argsRef.current, args)) return promiseRef.current;

      startRef.current = true;
      argsRef.current = args;

      cancel('continue');

      const source = Axios.CancelToken.source();
      cancelRef.current = source;

      // 100ms 防抖
      await wait(100);
      if (cancelRef.current !== source) return;

      setError(null);
      setData(null);
      setStatus('loading');
      upProgress(0);
      downProgress(0);

      let config = { ...opts, cancelToken: source.token, onUploadProgress, onDownloadProgress };
      let promise;
      if (typeof service === 'function') {
        promise = service(...args, config);
      } else {
        config = { baseURL: process.env.BASE_URL, ...service, ...config, ...args[0] };
        config.params ??= {};
        config.params = { ...config.params };
        config.params._ = Date.now();
        promise = Axios(config);
      }

      promise = promise
        .then(
          (res) => {
            if (promiseRef.current === promise) done(res);
            return res;
          },
          (error) => {
            if (promiseRef.current === promise) fail(error);
            return Promise.reject(error);
          },
        )
        .finally(() => {
          if (promiseRef.current === promise) {
            promiseRef.current = Promise.resolve(null);
            argsRef.current = null;
          }
        });

      promiseRef.current = promise;

      return promiseRef.current;
    },
    configOrServiceRef,
    optionsRef,
  );

  const startOnce = useCall((...args) => {
    if (!startRef.current) nextTick(start, ...args);
  });

  return useSimpleMemo({
    start,
    startOnce,
    cancel,
    status,
    result,
    error,
    uploadProgress,
    downloadProgress,
  });
};

export default useReq;
